package z.tool.service;

import java.sql.Connection;

import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

public class TransactionInterceptor implements MethodInterceptor {
    private static final String DEFAULT = "^(save|update|delete).*";

    private static final Log LOG = LogFactory.getLog(TransactionInterceptor.class);

    private static final ThreadLocal<TransactionStruct> TRANSACTION_STRUCTS = new ThreadLocal<TransactionStruct>();

    public static TransactionStruct getCurrentTransaction() {
        return TRANSACTION_STRUCTS.get();
    }

    private String pattern;

    public TransactionInterceptor() {
        this(DEFAULT);
    }

    public TransactionInterceptor(String pattern) {
        this.pattern = pattern;
    }

    public Object invoke(MethodInvocation invocation) throws Throwable {
        TransactionStruct struct = TRANSACTION_STRUCTS.get();

        // init struct
        if (null == struct) {
            boolean autoCommit = true;
            if (invocation.getMethod().getName().matches(pattern)) {
                autoCommit = false;
            }
            struct = new TransactionStruct(autoCommit);
            TRANSACTION_STRUCTS.set(struct);
            if (LOG.isDebugEnabled()) {
                LOG.debug("method:invoke,desc:construct new struct,autoCommit:"
                        + autoCommit + ",threadId:"
                        + Thread.currentThread().getId());
            }
        }

        try {
            // enter method
            struct.increStep();
            if (LOG.isDebugEnabled()) {
                LOG.debug("method:invoke,desc:enter method,methodName:"
                        + invocation.getMethod().getName() + ",step:"
                        + struct.getStep() + ",threadId:"
                        + Thread.currentThread().getId());
            }

            // call the target method
            return invocation.proceed();

        } catch (Exception e) {
            Connection connection = struct.getConnection();

            // try to release connection
            if (null != connection) {
                LOG.info("method:invoke,desc:inner handle connection,threadId:"
                                + Thread.currentThread().getId(), e);

                // rollback
                if (!struct.isAutoCommit()) {
                    try {
                        connection.rollback();
                    } catch (Exception er) {
                        if (LOG.isDebugEnabled()) {
                            LOG.debug("method:invoke,desc:inner rollback,threadId:" 
                                    + Thread.currentThread().getId(), er);
                        }
                    }
                }

                // close connection
                try {
                    connection.close();
                } catch (Exception er) {
                    if (LOG.isDebugEnabled()) {
                        LOG.debug("method:invoke,desc:inner close,threadId:"
                                + Thread.currentThread().getId(), er);
                    }
                }

                // set null
                struct.setConnection(null);
            }

            throw e;
        } finally {
            // left method
            struct.decreStep();
            if (LOG.isDebugEnabled()) {
                LOG.debug("method:invoke,desc:left method,methodName:"
                        + invocation.getMethod().getName() + ",step:"
                        + struct.getStep() + ",threadId:"
                        + Thread.currentThread().getId());
            }

            // finish method
            if (struct.isStepDone()) {

                // try to commit and close connection
                Connection connection = struct.getConnection();
                if (null != connection) {
                    // try to commit
                    if (!struct.isAutoCommit()) {
                        try {
                            connection.commit();
                        } catch (Exception ex) {
                            LOG.info("method:invoke,desc:commit,threadId:"
                                    + Thread.currentThread().getId(), ex);

                            // rollback
                            try {
                                connection.rollback();
                            } catch (Exception er) {
                                if (LOG.isDebugEnabled()) {
                                    LOG.debug("method:invoke,desc:rollback,threadId:"
                                            + Thread.currentThread().getId(), er);
                                }
                            }
                        }
                    }

                    // close connection
                    try {
                        connection.close();
                    } catch (Exception er) {
                        if (LOG.isDebugEnabled()) {
                            LOG.debug("method:invoke,desc:close,threadId:"
                                    + Thread.currentThread().getId(), er);
                        }
                    }
                }

                // remove struct
                TRANSACTION_STRUCTS.remove();
                if (LOG.isDebugEnabled()) {
                    LOG.debug("method:invoke,desc:remove struct,threadId:"
                            + Thread.currentThread().getId());
                }
            }
        }

    }
}
